// 选择器
// const ATTR_CONTEXT = '[data-range-dnd-context]';
const ATTR_ITEM = '[data-range-dnd-item]';
const ATTR_EXPAND_UP = '[data-range-dnd-expand-up]';
const ATTR_EXPAND_DOWN = '[data-range-dnd-expand-down]';
const ATTR_SCROLL = '[data-range-dnd-scroll]';

class DnD {
    // 事件类型
    eventType;

    // 事件map
    typeMap = {
        MOVE: 'move',
        EXPAND_UP: 'expandUp',
        EXPAND_DOWN: 'expandDown'
    };

    // 各种偏移量
    adjust = {
        x: 0, // target初始x
        y: 0, // target初始y
        h: 0, // target初始高度
        t: 0, // 距离context的上边距
        b: 0, // 距离context的下边距
        s: 0, // 当前滚动高度
        sh: 0 // 初始scrollHeight,拖动会影响
    };

    // 滚动dom
    scrollDom;

    // 是否在拖动中
    touching = false;

    // 是否有效点击 位移大于1px才是有效的
    validTouch = false;

    // 上下文dom
    context;

    // 最小唯一单位
    minUnit;

    // 暴露的事件回调
    onChange;

    onComplete;

    // 挂载拖动的目标属性
    target = {
        dom: null,
        index: null,
        offset: null
    };

    // 封装一下写style方法
    setStyle = (dom, opt) => {
        Object.keys(opt).forEach(key => {
            // eslint-disable-next-line no-param-reassign
            dom.style[key] = opt[key];
        });
    };

    // 兼容mouse和touch
    getPos = evt => {
        const { changedTouches } = evt;
        if (changedTouches) {
            const [{ pageX, pageY }] = changedTouches;
            return [pageX, pageY];
        }
        return [evt.pageX, evt.pageY];
    };

    // 移动的处理方法
    moveItem = () => {
        this.setStyle(this.target.dom, {
            transform: `translateY(${this.target.offset}px)`
        });
    };

    // 缩放的处理方法
    expandItem = type => {
        if (type === this.typeMap.EXPAND_UP) {
            this.setStyle(this.target.dom, {
                transform: `translateY(${this.target.offset}px)`,
                height: `${this.adjust.h - this.target.offset}px`
            });
        }
        if (type === this.typeMap.EXPAND_DOWN) {
            this.setStyle(this.target.dom, {
                height: `${this.adjust.h + this.target.offset}px`
            });
        }
    };

    isToBottom = () => this.scrollDom.scrollTop >= this.adjust.sh;

    // 移动&缩放时到达上下边界，滚动条进行滚动
    scrollContext = () => {
        if (!this.scrollDom) return;

        // 往下
        if (this.target.offset > 0) {
            // 滚动到底部
            if (this.isToBottom()) {
                this.scrollDom.scrollTop = this.adjust.sh;
                return;
            }

            let offsetBottom;
            if (this.eventType === this.typeMap.EXPAND_UP) {
                offsetBottom = this.target.offset + this.adjust.t - this.scrollDom.offsetHeight;
            } else {
                offsetBottom = this.target.offset - this.adjust.b;
            }
            this.scrollDom.scrollTop = this.adjust.s + (offsetBottom > 0 ? offsetBottom : 0);
        }
        // 往上
        if (this.target.offset < 0) {
            let offsetTop;
            if (this.eventType === this.typeMap.EXPAND_DOWN) {
                offsetTop = this.target.offset + this.scrollDom.offsetHeight - this.adjust.b;
            } else {
                offsetTop = this.target.offset + this.adjust.t;
            }
            this.scrollDom.scrollTop = this.adjust.s + (offsetTop < 0 ? offsetTop : 0);
        }
    };

    // 根据点击的target来判断事件类型
    getEventType = evt => {
        if (this.target.dom.querySelector(ATTR_EXPAND_UP).contains(evt.target)) {
            return this.typeMap.EXPAND_UP;
        }
        if (this.target.dom.querySelector(ATTR_EXPAND_DOWN).contains(evt.target)) {
            return this.typeMap.EXPAND_DOWN;
        }
        return this.typeMap.MOVE;
    };

    // 获取target在滚动容器中可见范围内的上下间隔
    getSpace = () => {
        const { y: itemY } = this.target.dom.getBoundingClientRect();
        const { y: contextY } = (this.scrollDom || this.context).getBoundingClientRect();
        return [itemY - contextY, contextY + this.scrollDom.offsetHeight - itemY - this.target.dom.offsetHeight];
    };

    // 处理点击开始
    handleStart = evt => {
        // 委托代理
        this.dragItems = Array.from(this.context.querySelectorAll(ATTR_ITEM));
        this.target.index = this.dragItems.findIndex(item => item.contains(evt.target));
        this.target.dom = this.dragItems[this.target.index];
        if (!this.target.dom) return;

        this.eventType = this.getEventType(evt);

        const [x, y] = this.getPos(evt);

        this.adjust.x = x;
        this.adjust.y = y;
        this.adjust.h = this.target.dom.offsetHeight;

        const [t, b] = this.getSpace();
        this.adjust.t = t;
        this.adjust.b = b;
        this.adjust.s = this.scrollDom?.scrollTop;

        this.touching = true;
    };

    // 获取事件对应handler
    moveHandleMap = {
        [this.typeMap.MOVE]: this.moveItem,
        [this.typeMap.EXPAND_DOWN]: () => this.expandItem(this.typeMap.EXPAND_DOWN),
        [this.typeMap.EXPAND_UP]: () => this.expandItem(this.typeMap.EXPAND_UP)
    };

    // 判断是否有效点击
    checkValidTouch = offset => Math.abs(offset) > 1;

    // 限制移动范围
    restrictOffset = offset => {
        // 往下
        if (offset > 0) {
            let standard;
            if (this.eventType === this.typeMap.EXPAND_UP) {
                standard = this.adjust.h;
                if (this.minUnit) standard -= this.minUnit;
            } else {
                standard = this.adjust.b - this.adjust.s + this.adjust.sh;
            }

            if (offset >= standard) return standard;
        }

        // 往上
        if (offset < 0) {
            let standard;
            if (this.eventType === this.typeMap.EXPAND_DOWN) {
                standard = this.adjust.h;
                if (this.minUnit) standard -= this.minUnit;
            } else {
                standard = this.adjust.t + this.adjust.s;
            }

            if (-offset >= standard) return -standard;
        }
        return offset;
    };

    // 限制刻度,但是UI-DATA的交互并不精确,需要两侧都做调整,这里紧紧是UI
    limitScaleOffset = offset => {
        if (!this.minUnit) return offset;
        // return offset;
        return Math.floor(offset / this.minUnit) * this.minUnit;
    };

    // 处理移动以及回调
    handleMove = evt => {
        if (!this.target.dom || !this.touching) return;
        evt.preventDefault();

        const [, y] = this.getPos(evt);
        const offset = y - this.adjust.y;
        this.validTouch = this.checkValidTouch(offset);
        if (!this.validTouch) return;

        let nextOffset = this.restrictOffset(offset);
        nextOffset = this.limitScaleOffset(nextOffset);

        this.target.offset = nextOffset;

        this.moveHandleMap[this.eventType]();
        this.scrollContext();

        if (this.onChange) {
            this.onChange(this.target.index, this.target.offset, this.eventType);
        }
    };

    // 处理移动后的状态重置以及回调
    handleEnd = () => {
        if (!this.target.dom || !this.touching || !this.validTouch) {
            this.target.dom = null;
            return;
        }

        this.touching = false;
        this.validTouch = false;

        if (this.onComplete) {
            this.onComplete(this.target.index, this.target.offset, this.eventType);
            this.setStyle(this.target.dom, {
                transform: ''
            });
        }
        this.target.dom = null;
    };

    // 绑定事件
    addEvent = () => {
        document.addEventListener('mousedown', this.handleStart);
        document.addEventListener('mousemove', this.handleMove, true);
        document.addEventListener('mouseup', this.handleEnd);
        document.addEventListener('touchstart', this.handleStart, { passive: false });
        document.addEventListener('touchmove', this.handleMove, { passive: false });
        document.addEventListener('touchend', this.handleEnd, { passive: false });
    };

    // 处理拖动造成的scrollHeight变化
    resize = () => {
        setTimeout(() => {
            this.adjust.sh = this.scrollDom?.scrollHeight - this.scrollDom?.clientHeight;
        });
    };

    // 动态设置option
    setOption = (option = {}) => {
        const availableOption = ['minUnit', 'onChange', 'onComplete', 'context'];
        availableOption.forEach(item => {
            if (option[item]) this[item] = option[item];
        });
    };

    // 初始化
    initialize = (option = {}) => {
        this.setOption(option);
        if (this.context) {
            this.addEvent();
            this.scrollDom = document.querySelector(ATTR_SCROLL);
            this.resize();
        }
    };

    // 销毁事件绑定
    destroy = () => {
        if (!this.context) return;
        document.removeEventListener('mousedown', this.handleStart);
        document.removeEventListener('mousemove', this.handleMove);
        document.removeEventListener('mouseup', this.handleEnd);
        document.removeEventListener('touchstart', this.handleStart);
        document.removeEventListener('touchmove', this.handleMove);
        document.removeEventListener('touchend', this.handleEnd);
    };
}

export default DnD;
